package util

import (
	"errors"
	"fmt"
	"github.com/spf13/cast"
	"github.com/spf13/viper"
	"log"
	"remote_exec/goterm/term"
	"strings"
)

type Config struct {
	Port         int      `default:"22" mapstructure:"port,omitempty"`
	RootPrompt   string   `mapstructure:"root-prompt,omitempty"`
	PasswdPrompt string   `mapstructure:"passwd-prompt,omitempty"`
	Timeout      int      `default:"10" mapstructure:"timeout,omitempty"`
	User         string   `mapstructure:"user,omitempty"`
	Passwd       string   `mapstructure:"passwd,omitempty"`
	RootPasswd   string   `mapstructure:"root-passwd,omitempty"`
	Hosts        []string `mapstructure:"hosts,omitempty"`
	SpcHosts     []string `mapstructure:"spc-hosts,omitempty"`
	IsEncrypt    bool     `default:"false" mapstructure:"is-encrypt,omitempty"`
}

// LoadCfg 加载配置文件为Viper对象
func LoadCfg(configPath string, defaultConfig string) (*viper.Viper, error) {
	var (
		v       = viper.New()
		cfgPath string
	)
	if r, _ := FileExists(nil, configPath); r {
		cfgPath = configPath
	} else {
		log.Println(term.Yellowf("config file %s not found, will use %s.", configPath, defaultConfig))
		cfgPath = defaultConfig
	}

	v.SetConfigFile(cfgPath)
	if err := v.ReadInConfig(); err != nil {
		if _, ok := err.(viper.ConfigFileNotFoundError); ok {
			return nil, fmt.Errorf("config file %s not found", cfgPath)
		} else {
			return nil, err
		}
	}

	return v, nil
}

// ParseConfig 加载config.yaml配置文件为Viper对象
func ParseConfig(configPath string) (*Config, error) {
	var (
		err    error
		viper  *viper.Viper
		config *Config
	)

	if viper, err = LoadCfg(configPath, DefaultConfig); err != nil {
		return nil, err
	}

	if viper.Unmarshal(&config) != nil {
		return nil, err
	}

	return config, nil
}

// ParseHosts parse host info from config.yaml
func ParseHosts(configPath string) ([]*Host, error) {
	var (
		err     error
		config  *Config
		hosts   []*Host
		port    int
		user    string
		passwd  string
		rootPwd string
	)
	if config, err = ParseConfig(configPath); err != nil {
		return hosts, err
	}

	if config.IsEncrypt {
		if passwd, err = InputPasswd(); err != nil {
			return nil, errors.New("an error occurred in input the password")
		}
		if err := config.DecryptConfig(passwd); err != nil {
			return nil, errors.New("password is error, decrypt config failed")
		}
	}

	port = config.Port
	user = config.User
	passwd = config.Passwd
	rootPwd = config.RootPasswd

	for _, host := range config.Hosts {
		h := &Host{Port: port, Host: host, User: user, Passwd: passwd, RootPwd: rootPwd}
		hosts = append(hosts, h)
	}

	if len(config.SpcHosts) > 0 {
		for _, spcHost := range config.SpcHosts {
			params := strings.Split(spcHost, " ")
			h := &Host{User: user, Host: params[0], Port: cast.ToInt(params[1]), Passwd: params[2], RootPwd: params[3]}
			hosts = append(hosts, h)
		}
	}
	return hosts, nil
}

func (config *Config) EncryptConfig(passwd string) error {
	if config.IsEncrypt {
		return nil
	}
	var err error
	var hosts []string

	realPwd := PKCS7Pad([]byte(passwd), 32)

	if len(config.SpcHosts) > 0 {
		for _, spcHost := range config.SpcHosts {
			params := strings.Split(spcHost, " ")

			var encPasswd1 string
			if len(params[2]) > 0 {
				encPasswd1, err = EncryptString(params[2], realPwd)
				if err != nil {
					return err
				}
			}

			var encPasswd2 string
			if len(params[3]) > 0 {
				encPasswd2, err = EncryptString(params[3], realPwd)
				if err != nil {
					return err
				}
			}
			hosts = append(hosts, strings.Join([]string{params[0], params[1], encPasswd1, encPasswd2}, " "))
		}
	}

	var encPasswd string
	if len(config.Passwd) > 0 {
		encPasswd, err = EncryptString(config.Passwd, realPwd)
		if err != nil {
			return err
		}
	}

	var encRootPwd string
	if len(config.RootPasswd) > 0 {
		encRootPwd, err = EncryptString(config.RootPasswd, realPwd)
		if err != nil {
			return err
		}
	}

	config.IsEncrypt = true
	config.SpcHosts = hosts
	config.Passwd = encPasswd
	config.RootPasswd = encRootPwd
	return nil
}

func (config *Config) DecryptConfig(passwd string) error {
	if !config.IsEncrypt {
		return nil
	}
	var err error
	var hosts []string

	realPwd := PKCS7Pad([]byte(passwd), 32)

	if len(config.SpcHosts) > 0 {
		for _, spcHost := range config.SpcHosts {
			params := strings.Split(spcHost, " ")

			var decPasswd1 string
			if len(params[2]) > 0 {
				decPasswd1, err = DecryptString(params[2], realPwd)
				if err != nil {
					return err
				}
			}

			var decPasswd2 string
			if len(params[3]) > 0 {
				decPasswd2, err = DecryptString(params[3], realPwd)
				if err != nil {
					return err
				}
			}
			hosts = append(hosts, strings.Join([]string{params[0], params[1], decPasswd1, decPasswd2}, " "))
		}
	}

	var decPasswd string
	if len(config.Passwd) > 0 {
		decPasswd, err = DecryptString(config.Passwd, realPwd)
		if err != nil {
			return err
		}
	}

	var decRootPwd string
	if len(config.RootPasswd) > 0 {
		decRootPwd, err = DecryptString(config.RootPasswd, realPwd)
		if err != nil {
			return err
		}
	}

	config.IsEncrypt = false
	config.SpcHosts = hosts
	config.Passwd = decPasswd
	config.RootPasswd = decRootPwd
	return nil
}

func (config *Config) WriteConfig(configPath string) error {
	var (
		err     error
		viper   *viper.Viper = viper.New()
		cfgPath string       = configPath
	)

	if r, _ := FileExists(nil, configPath); r {
		cfgPath = configPath
	} else {
		log.Println(term.Yellowf("config file %s not found, will use %s.", configPath, DefaultConfig))
		cfgPath = DefaultConfig
	}

	viper.Set("is-encrypt", config.IsEncrypt)
	viper.Set("port", config.Port)
	viper.Set("root-prompt", config.RootPrompt)
	viper.Set("passwd-prompt", config.PasswdPrompt)
	viper.Set("timeout", config.Timeout)
	viper.Set("user", config.User)
	viper.Set("passwd", config.Passwd)
	viper.Set("root-passwd", config.RootPasswd)
	viper.Set("hosts", config.Hosts)
	if len(config.SpcHosts) > 0 {
		viper.Set("spc-hosts", config.SpcHosts)
	}

	if err = viper.WriteConfigAs(cfgPath); err != nil {
		return errors.New(err.Error())
	}

	return nil
}
